/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.atlascon.java2gpb.serialization;

import com.google.common.base.Preconditions;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Tom
 */
public class GPBObjectSerializer extends AbstractGPBSerializer {

    private static final Logger LOGGER = LoggerFactory.getLogger(GPBObjectSerializer.class);

    public GPBObjectSerializer(GPBSerializers serializers) {
        super(serializers);
    }

    public Builder serialize(Object o, Class<?> cls, Builder builder) throws Exception {
        // fields and methods       
        // TODO PARENT CLASS ASSIGN TO CURRENT!
        SerializationInspector ins = SerializationInspector.get(cls);
        Map<Field, GPBSerializable> fields = ins.getDeclaredFields();
        Map<Method, GPBSerializable> methods = ins.getDeclaredMethods();
        setFields(fields, o, builder);
        setMethods(methods, o, builder);
        // super class?
        String parentField = ins.getAnnotation().parentField();
        if (!parentField.trim().isEmpty() && cls.getSuperclass() != null && SerializationInspector.get(cls.getSuperclass()).hasAnnotation()) {
            cls = cls.getSuperclass();
            ins = SerializationInspector.get(cls);
            Builder supBuilder = ins.newBuilder();
            serialize(o, cls, supBuilder);
            GPBSerializers.setField(parentField, supBuilder.build(), builder);
        }
        // serialize
        return (Builder) builder;
    }

    private void setFields(Map<Field, GPBSerializable> items, Object obj, Builder builder) throws Exception {
        for (Field f : items.keySet()) {
            GPBSerializable a = items.get(f);
            GPBSerializer serializer = getSerializers().getSerializer(a, f.getType());
            Object val = f.get(obj);
            String name = getName(a, f.getName());
            serializer.serialize(name, val, builder);
        }
    }

    private void setMethods(Map<Method, GPBSerializable> items, Object obj, Builder builder) throws Exception {
        for (Method m : items.keySet()) {
            GPBSerializable a = items.get(m);
            GPBSerializer serializer = getSerializers().getSerializer(a, m.getReturnType());
            Object val = m.invoke(obj);
            String name = getName(a, m.getName());
            serializer.serialize(name, val, builder);
        }
    }

    private String getName(GPBSerializable a, String name) {
        if (a.name().trim().isEmpty()) {
            return name;
        } else {
            return a.name();
        }
    }

    @Override
    public void serialize(String name, Object obj, Builder parentBuilder) throws Exception {
        if (obj != null) {
            Builder objBuilder = SerializationInspector.get(obj.getClass()).newBuilder();
            objBuilder = serialize(obj, obj.getClass(), objBuilder);
            Descriptors.FieldDescriptor fd = parentBuilder.getDescriptorForType().findFieldByName(name);
            if (fd == null) {
                LOGGER.warn("Unable to get FieldDescriptor for field " + name + " for " + parentBuilder.getDescriptorForType().getFullName());
            }
            if (fd.isRepeated()) {
                parentBuilder.addRepeatedField(fd, objBuilder.build());
            } else {
                parentBuilder.setField(fd, objBuilder.build());
            }
        }
    }

    @Override
    public Object deserialize(ClassInfo cls, Object gpbValue) throws Exception {
        Preconditions.checkNotNull(cls);
        Preconditions.checkNotNull(gpbValue);
        if (!(gpbValue instanceof Message)) {
            throw new IllegalArgumentException("Invalid gpbValue " + gpbValue.getClass());
        }
        Message msg = (Message) gpbValue;
        Object obj = cls.getFieldClass().newInstance();
//        System.out.println("Deserialize "+cls+" from "+msg.getDescriptorForType().getName());
        SerializationInspector ins = SerializationInspector.get(cls.getFieldClass());
        Map<Field, GPBSerializable> declaredFields = ins.getDeclaredFields();
        for (Field f : declaredFields.keySet()) {
            String name = ins.getFieldName(f);
            Class paramCls = ins.getFieldParamClass(f);
//            System.out.println("Field name:"+f.getName()+", gpbName: "+name +", type: "+f.getType()+", paramType: "+paramCls);
            GPBSerializer serializer = getSerializers().getSerializer(declaredFields.get(f), f.getType());
//            System.out.println("Using serializer: "+serializer);            
            FieldDescriptor fd = msg.getDescriptorForType().findFieldByName(name);
            if ((fd.isRepeated() && (!((Collection) msg.getField(fd)).isEmpty())) || fd.isRequired() || (fd.isOptional() && msg.hasField(fd))) {
                Object val = msg.getField(fd);
                Object de = serializer.deserialize(ClassInfo.get(f.getType(), paramCls), val);
//            System.out.println("Deserialized: "+de);
                f.set(obj, de);
            }
//            System.out.println("Set to object...");
        }
        return obj;
    }

    @Override
    public String toString() {
        return "ObjectSerializer";
    }
}
